package org.belkodevelop.user.service;

import org.belkodevelop.user.domain.User;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.belkodevelop.mail.Mail;
import org.belkodevelop.mail.MailTemplate;
import org.belkodevelop.mail.SimpleMailSender;
import org.belkodevelop.role.dao.RoleDAO;
import org.belkodevelop.token.domain.Token;
import org.belkodevelop.token.exception.TokenNotFoundException;
import org.belkodevelop.token.service.TokenService;
import org.belkodevelop.user.dao.UserDAO;
import org.belkodevelop.user.exception.ErrorSendMail;
import org.belkodevelop.user.exception.NotFoundAdmin;
import org.belkodevelop.user.exception.UserExistsException;
import org.belkodevelop.user.exception.UserNotFoundException;
import org.belkodevelop.userdatails.adapter.UserDetailsAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService{

    private final Log log = LogFactory.getLog(getClass()); 
    
    @Value("${web.token.life_time}")
    private int lifeTimeToken;
    
    @Autowired
    private UserDAO userDAO;
    
    @Autowired
    private RoleDAO roleDAO;
    
    @Autowired
    private TokenService tokenService;
    
    
    @Autowired
    private SaltSource saltSource;
    
    @Autowired
    private PasswordEncoder passwordEncoder; 
    
    @Autowired
    private SimpleMailSender mailSender;
    
    @Autowired
    private MailTemplate registrationPatientMailTemplate;

    @Autowired
    private MailTemplate registrationDoctorMailTemplate;
    
    @Autowired
    private MailTemplate recoveryPasswordMailTemplate;

    @Autowired
    private MailTemplate registrationDoctorAdminMailTemplate;
    
    @Transactional
    private void addUser(User user) {
        userDAO.saveUser(user);
        UserDetailsAdapter userDetails = new UserDetailsAdapter(user);
        String password = userDetails.getPassword();
        Object salt = saltSource.getSalt(userDetails);
        user.setPassword(passwordEncoder.encodePassword(password, salt));
        userDAO.saveUser(user);
    }

    @Transactional
    public boolean connectPatientToDoctor(long userId, String emailDoctor) {
        return userDAO.connectPatientToDoctor(userId, emailDoctor);
    }

    @Transactional
    public void resetConnectPatientToDoctor(long userId){
        userDAO.resetConnectPatientToDoctor(userId);
    }

    private boolean connectPatientToDoctor(User user, String emailDoctor) {
        User doctor = userDAO.getUser(emailDoctor);
        if (doctor != null) {
            user.setDoctor(doctor);
            return true;
        }
        return false;
    }

    @Transactional
    public void registerPatient(User user, String emailDoctor) throws UserExistsException {
        if (userDAO.getUser(user.getEmail()) != null) {
            throw new UserExistsException();
        }
        user.setRole(roleDAO.getRole("ROLE_PATIENT"));

        connectPatientToDoctor(user, emailDoctor);

        addUser(user);

        Mail mail = registrationPatientMailTemplate.createMail(user.getEmail());
        mail.addObject("user", user);
        try {
            mailSender.send(mail);
        } catch (Exception ex) {
            log.warn("Error sending mail!", ex);
        }
    }
    
    @Transactional
    public void registerAdmin(User user) throws UserExistsException {
        if (userDAO.getUser(user.getEmail()) != null) {
            throw new UserExistsException();
        }
        user.setRole(roleDAO.getRole("ROLE_ADMIN"));
        addUser(user);
    }

    @Transactional(readOnly = true)
    public List<User> getAllAdmins() {
        return userDAO.getAllAdminUsers();
    }

    @Transactional(readOnly = true)
    public List<User> getAllPatients() {
        return userDAO.getAllPatientUsers();
    }

    @Transactional(readOnly = true)
    public List<User> getAllDoctors() {
        return userDAO.getAllDoctorUsers();
    }

    @Transactional
    public void registerDoctor(User user) throws UserExistsException {
        if (userDAO.getUser(user.getEmail()) != null) {
            throw new UserExistsException();
        }
        user.setRole(roleDAO.getRole("ROLE_DOCTOR"));
        user.setEnabled(false);
        addUser(user);

        List<User> admins = new ArrayList<User>();
        for (User admin: getAllAdmins()) {
            if (admin.isEnabled()) {
                admins.add(admin);
            }
        }

        if (admins.isEmpty()) {
            throw new NotFoundAdmin();
        }

        Mail mailToAdmins = registrationDoctorAdminMailTemplate.createMail(admins);
        Mail mailToDoctor = registrationDoctorMailTemplate.createMail(user.getEmail());

        mailToAdmins.addObject("user", user);
        mailToDoctor.addObject("user", user);

        try {
            mailSender.send(mailToAdmins);
            mailSender.send(mailToDoctor);
        } catch (Exception ex) {
            log.warn("Error sending mail!", ex);
            throw new ErrorSendMail(ex);
        }
    }

    @Transactional(readOnly = true)
    public List<User> listUsers() {
        return userDAO.listUsers();
    }
    
    @Transactional(readOnly = true)
    public User getUserByLogin(String login) throws UserNotFoundException {
        User user = userDAO.getUser(login);
        if (user == null) {
            throw new UserNotFoundException();
        }
        return user;
    }
    
    @Transactional(readOnly = true)
    public User getUserById(long id) throws UserNotFoundException {
        User user = userDAO.getUser(id);
        if (user == null) {
            throw new UserNotFoundException();
        }
        return user;
    }
    
    @Transactional
    public void sendRecoveryPasswordMail(String email) throws ErrorSendMail {
        User user = userDAO.getUser(email);
        if (user != null) {
            Mail mail = recoveryPasswordMailTemplate.createMail(email);
            Token token;
            try {
                token = tokenService.createToken(Long.toString(user.getId()));
            } catch(Exception ex) {
                throw new ErrorSendMail(ex);
            }
            mail.addObject("user", user);
            mail.addObject("token", token);
            try {
                mailSender.send(mail);
            } catch (Exception ex) {
                log.warn("Error sending mail!", ex);
                throw new ErrorSendMail(ex);
            }        
        }
    }
    
    @Transactional
    public boolean checkRecoveryPasswordToken(String login, String tokenValue) {
        try {
            User user = this.getUserByLogin(login);
            return tokenService.checkToken(tokenValue, Long.toString(user.getId()), lifeTimeToken);
        } catch (TokenNotFoundException | UserNotFoundException ex) {
            return false;
        }
    }
    
    @Transactional
    public void changePassword(long userId, String password) {
        User user = userDAO.getUser(userId);
        user.setPassword(password);
        UserDetailsAdapter userDetails = new UserDetailsAdapter(user);
        Object salt = saltSource.getSalt(userDetails);
        user.setPassword(passwordEncoder.encodePassword(userDetails.getPassword(), salt));
        userDAO.saveUser(user);
    }
}
